/////////////////////////////////////////////////////////////////////////////
//
//! @file    mip_sdk_user_functions.c (linux version)
//! @author  Gregg Carpenter
//! @version 1.0
//
//! @description Target-Specific Functions Required by the MIP SDK
//
// External dependencies:
//
//  mip.h
//
//! @copyright 2011 Microstrain.
//
//!@section LICENSE
//!
//! THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING
//! CUSTOMERS WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER
//! FOR THEM TO SAVE TIME. AS A RESULT, MICROSTRAIN SHALL NOT BE HELD LIABLE
//! FOR ANY DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY
//! CLAIMS ARISING FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY
//! CUSTOMERS OF THE CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH
//! THEIR PRODUCTS.
//
/////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//
// Include Files
//
////////////////////////////////////////////////////////////////////////////////

#include "mip_sdk_user_functions.h"

/////////////////////////////////////////////////////////////////////////////
//
//! @fn
//! u16 purge(ComPortHandle comPortHandle)
//
//! @section DESCRIPTION
//! Target-Specific communications port purge.
//
//! @section DETAILS
//!
//! @param [in] int comPortHandle  - port number (as recognized by the
//!                                                          operating system.)
//
//! @retval MIP_USER_FUNCTION_ERROR  When there is a problem purging the
//!                                                                     port.\n
//! @retval MIP_USER_FUNCTION_OK     Purge was successful.\n
//
//! @section NOTES
//!
//! None
//!
//
/////////////////////////////////////////////////////////////////////////////

u16 purge(ComPortHandle comPortHandle) {
  if (tcflush(comPortHandle, TCIOFLUSH) == -1) {
    printf("flush failed\n");
    return MIP_USER_FUNCTION_ERROR;
  }

  return MIP_USER_FUNCTION_OK;
}

/////////////////////////////////////////////////////////////////////////////
//
//! @fn
//! u16 mip_sdk_port_open(void *port_handle, int port_num, int baudrate)
//
//! @section DESCRIPTION
//! Target-Specific communications port open.
//
//! @section DETAILS
//!
//! @param [out] void *port_handle - target-specific port handle pointer (user
//!                                          needs to allocate memory for this)
//! @param [in] int port_num       - port number (as recognized by the
//!                                                          operating system.)
//! @param [in] int baudrate       - baudrate of the com port.
//
//! @retval MIP_USER_FUNCTION_ERROR  When there is a problem opening the
//!                                                                     port.\n
//! @retval MIP_USER_FUNCTION_OK     The open was successful.\n
//
//! @section NOTES
//!
//! None
//!
//
/////////////////////////////////////////////////////////////////////////////

u16 mip_sdk_port_open(void** port_handle, int port_num, int baudrate) {
  char port_name[100] = {0};
  char port_index[100] = {0};
  static ComPortHandle local_port_handle;
  int hardware_bit_baud, status;
  struct termios options;

#ifdef USE_USB_CONNECTION

  // all USB connections appear on ttyACM#
  strcat(port_name, "/dev/ttyACM");

#else

  // USART connections occur on ttyS#
  strcat(port_name, "/dev/ttyS");

#endif

  // construct port filename address string
  sprintf(&port_index[0], "%u", port_num);
  strcat(port_name, port_index);

  printf("Attempting to open port: %s\n", port_name);
  // Attempt to open the specified port
  local_port_handle = open(port_name, O_RDWR | O_NOCTTY);

  // Check for an invalid handle
  if (local_port_handle == -1) {
    printf("Unable to open com Port %s\n Errno = %i\n", port_name, errno);

    return MIP_USER_FUNCTION_ERROR;
  }
  printf("Port: %s opened successfully.\n", port_name);

  // Convert specified baud to hardware specific value
  switch (baudrate) {
    case 0:
      hardware_bit_baud = B0;
      break;

    case 50:
      hardware_bit_baud = B50;
      break;

    case 75:
      hardware_bit_baud = B75;
      break;

    case 110:
      hardware_bit_baud = B110;
      break;

    case 134:
      hardware_bit_baud = B134;
      break;

    case 150:
      hardware_bit_baud = B150;
      break;

    case 200:
      hardware_bit_baud = B200;
      break;

    case 300:
      hardware_bit_baud = B300;
      break;

    case 600:
      hardware_bit_baud = B600;
      break;

    case 1200:
      hardware_bit_baud = B1200;
      break;

    case 1800:
      hardware_bit_baud = B1800;
      break;

    case 2400:
      hardware_bit_baud = B2400;
      break;

    case 4800:
      hardware_bit_baud = B4800;
      break;

    case 9600:
      hardware_bit_baud = B9600;
      break;

    case 19200:
      hardware_bit_baud = B19200;
      break;

    case 38400:
      hardware_bit_baud = B38400;
      break;

#ifdef B7200
    case 7200:
      hardware_bit_baud = B7200;
      break;

#endif

#ifdef B14400
    case 14400:
      hardware_bit_baud = B14400;
      break;

#endif

#ifdef B57600
    case 57600:
      hardware_bit_baud = B57600;
      break;

#endif

#ifdef B115200
    case 115200:
      hardware_bit_baud = B115200;
      break;

#endif

#ifdef B230400
    case 230400:
      hardware_bit_baud = B230400;
      break;

#endif

#ifdef B460800
    case 460800:
      hardware_bit_baud = B460800;
      break;

#endif

#ifdef B500000
    case 500000:
      hardware_bit_baud = B500000;
      break;

#endif

#ifdef B576000
    case 576000:
      hardware_bit_baud = B576000;
      break;

#endif

#ifdef B921600
    case 921600:
      hardware_bit_baud = B921600;
      break;

#endif

#ifdef B1000000
    case 1000000:
      hardware_bit_baud = B1000000;
      break;

#endif

#ifdef B1152000
    case 1152000:
      hardware_bit_baud = B1152000;
      break;

#endif

#ifdef B2000000
    case 2000000:
      hardware_bit_baud = B2000000;
      break;

#endif

#ifdef B3000000
    case 3000000:
      hardware_bit_baud = B3000000;
      break;

#endif

#ifdef B3500000
    case 3500000:
      hardware_bit_baud = B3500000;
      break;

#endif

#ifdef B4000000
    case 4000000:
      hardware_bit_baud = B4000000;
      break;

#endif
    // Unsupported baud specified
    default:
      printf("Unsupported baud specified\n");
      return MIP_USER_FUNCTION_ERROR;
  }

  // Get the current settings for the port...
  tcgetattr(local_port_handle, &options);

  // set the baud rate
  cfsetospeed(&options, hardware_bit_baud);
  cfsetispeed(&options, hardware_bit_baud);

  // set the number of data bits.
  options.c_cflag &= ~CSIZE;  // Mask the character size bits
  options.c_cflag |= CS8;

  // set the number of stop bits to 1
  options.c_cflag &= ~CSTOPB;

  // Set parity to None
  options.c_cflag &= ~PARENB;

  // set for non-canonical (raw processing, no echo, etc.)
  options.c_iflag = IGNPAR;  // ignore parity check close_port(int
  options.c_oflag = 0;       // raw output
  options.c_lflag = 0;       // raw input

  // Time-Outs -- won't work with NDELAY option in the call to open
  options.c_cc[VMIN] = 0;   // block reading until RX x characers. If x = 0,
                            // it is non-blocking.
  options.c_cc[VTIME] = 1;  // Inter-Character Timer -- i.e. timeout= x*.1 s

  // Set local mode and enable the receiver
  options.c_cflag |= (CLOCAL | CREAD);

  // purge serial port buffers
  if (purge(local_port_handle) != MIP_USER_FUNCTION_OK) {
    printf("Flushing old serial buffer data failed\n");
    return MIP_USER_FUNCTION_ERROR;
  }
  // attempt to apply settings
  status = tcsetattr(local_port_handle, TCSANOW, &options);

  if (status != 0) {  // For error message

    printf("Configuring comport failed\n");
    return MIP_USER_FUNCTION_ERROR;
  }

  // Purge serial port buffers
  if (purge(local_port_handle) != MIP_USER_FUNCTION_OK) {
    printf("Post configuration serial buffer flush failed\n");
    return MIP_USER_FUNCTION_ERROR;
  }

  // assign external pointer to port handle pointer
  *port_handle = &local_port_handle;

  return MIP_USER_FUNCTION_OK;
}

/////////////////////////////////////////////////////////////////////////////
//
//! @fn
//! u16 mip_sdk_port_close(void *port_handle)
//
//! @section DESCRIPTION
//! Target-Specific port close function
//
//! @section DETAILS
//!
//! @param [in] void *port_handle - target-specific port handle pointer (user
//!                                          needs to allocate memory for this)
//
//! @retval MIP_USER_FUNCTION_ERROR  When there is a problem closing the
//!                                                                     port.\n
//! @retval MIP_USER_FUNCTION_OK     The close was successful.\n
//
//! @section NOTES
//!
//! None
//!
//
/////////////////////////////////////////////////////////////////////////////

u16 mip_sdk_port_close(void* port_handle) {
  int local_port_handle = *((int*)port_handle);

  if (port_handle == NULL) return MIP_USER_FUNCTION_ERROR;

  // Close the serial port
  close(local_port_handle);

  return MIP_USER_FUNCTION_OK;
}

/////////////////////////////////////////////////////////////////////////////
//
//! @fn
//! u16 mip_sdk_port_write(void *port_handle, u8 *buffer, u32 num_bytes,
//!                                         u32 *bytes_written, u32 timeout_ms)
//
//! @section DESCRIPTION
//! Target-Specific Port Write Function.
//
//! @section DETAILS
//!
//! @param [in]  void *port_handle  - target-specific port handle pointer
//!                                    (user needs to allocate memory for this)
//! @param [in]  u8 *buffer         - buffer containing num_bytes of data
//! @param [in]  u32 num_bytes      - the number of bytes to write to the port
//! @param [out] u32 *bytes_written - the number of bytes actually written to
//!                                                                    the port
//! @param [in]  u32 timeout_ms     - the write timeout
//
//! @retval MIP_USER_FUNCTION_ERROR  When there is a problem communicating with
//!                                                                 the port.\n
//! @retval MIP_USER_FUNCTION_OK     The write was successful.\n
//
//! @section NOTES
//!
//! None
//!
//
/////////////////////////////////////////////////////////////////////////////

u16 mip_sdk_port_write(void* port_handle, u8* buffer, u32 num_bytes, u32* bytes_written, u32 timeout_ms) {
  int local_port_handle = *((int*)port_handle);
  static char print_once = 0;

  if (print_once == 0) {
    printf("local_port_handle = %lx ", local_port_handle);
    printf("port_handle = %lx \n", (int*)port_handle);
    print_once = 1;
  }

  int local_bytes_written = write(local_port_handle, buffer, num_bytes);

  if (local_bytes_written == -1) return MIP_USER_FUNCTION_ERROR;

  *bytes_written = local_bytes_written;
  if (*bytes_written == num_bytes)
    return MIP_USER_FUNCTION_OK;
  else
    return MIP_USER_FUNCTION_ERROR;
}

/////////////////////////////////////////////////////////////////////////////
//
//! @fn
//! u16 mip_sdk_port_read(void *port_handle, u8 *buffer, u32 num_bytes,
//!                                            u32 *bytes_read, u32 timeout_ms)
//
//! @section DESCRIPTION
//! Target-Specific Port Write Function.
//
//! @section DETAILS
//!
//! @param [in]  void *port_handle  - target-specific port handle pointer
//!                                    (user needs to allocate memory for this)
//! @param [in]  u8 *buffer         - buffer containing num_bytes of data
//! @param [in]  u32 num_bytes      - the number of bytes to write to the port
//! @param [out] u32 *bytes_read    - the number of bytes actually read from
//!                                                                  the device
//! @param [in]  u32 timeout_ms     - the read timeout
//
//! @retval MIP_USER_FUNCTION_ERROR  When there is a problem communicating with
//!                                                                 the port.\n
//! @retval MIP_USER_FUNCTION_OK     The read was successful.\n
//
//! @section NOTES
//!
//! None
//!
//
/////////////////////////////////////////////////////////////////////////////

u16 mip_sdk_port_read(void* port_handle, u8* buffer, u32 num_bytes, u32* bytes_read, u32 timeout_ms) {
  int local_port_handle = *((int*)port_handle);

  int local_bytes_read = read(local_port_handle, buffer, num_bytes);

  *bytes_read = local_bytes_read;
  if (*bytes_read == num_bytes)
    return MIP_USER_FUNCTION_OK;
  else
    return MIP_USER_FUNCTION_ERROR;
}

/////////////////////////////////////////////////////////////////////////////
//
//! @fn
//! u32 mip_sdk_port_read_count(void *port_handle)
//
//! @section DESCRIPTION
//! Target-Specific Function to Get the Number of Bytes Waiting on the Port.
//
//! @section DETAILS
//!
//! @param [in]  void *port_handle  - target-specific port handle pointer
//                                     (user needs to allocate memory for this)
//
//! @returns  Number of bytes waiting on the port,\n
//!           0, if there is an error.
//
//! @section NOTES
//!
//! None
//!
//
/////////////////////////////////////////////////////////////////////////////

u32 mip_sdk_port_read_count(void* port_handle) {
  int local_port_handle = *((int*)port_handle);

  int bytes_available;
  ioctl(local_port_handle, FIONREAD, &bytes_available);
  return (u32)bytes_available;
}

/////////////////////////////////////////////////////////////////////////////
//
//! @fn
//! u32 mip_sdk_get_time_ms()
//
//! @section DESCRIPTION
//! Target-Specific Call to get the current time in milliseconds.
//
//! @section DETAILS
//!
//! @param [in]  void *port_handle  - target-specific port handle pointer
//                                     (user needs to allocate memory for this)
//
//! @returns  Current time in milliseconds.
//
//! @section NOTES
//!
//!   1) This value should not roll-over in short periods of time
//!                                                            (e.g. minutes)\n
//!   2) Most systems have a millisecond counter that rolls-over every 32
//!                                                                      bits\n
//!      (e.g. 49.71 days roll-over period, with 1 millisecond LSB)\n
//!   3) An absolute reference is not required since this function is\n
//!      used for relative time-outs.
//!
//
/////////////////////////////////////////////////////////////////////////////

u32 mip_sdk_get_time_ms() {
  struct timespec ts;

  if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
    return -1;
  }

  return (u32)(ts.tv_sec * 1000ll + ((ts.tv_nsec / 1000000)));
}
